TP : analyse d'images¶

Table of contents

  • Manipulation d'histogrammes
    • Préambule
    • Exercice 1
    • Exercice 2
    • Exercice 3 : Correction gamma
  • Filtrage d'images
    • Préambule
    • Exercice 4
    • Exercice 5
  • Seuillage d'image
    • Exercice 6
    • Exercice 7

Manipulation d'histogrammes¶

Dans cette partie du TP, les fonctions OpenCV utilisées/utilisables sont les suivantes :

  • cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) -> hist
  • cv2.normalize(src, dst[, alpha[, beta[, norm_type[, dtype[, mask]]]]]) -> dst
  • cv2.equalizeHist(src[, dst]) -> dst
  • cv2.LUT(src, lut[, dst]) ->dst

Préambule¶

Un histogramme sera représenté en mémoire avec un tableau nd de numpy de 255 lignes et 1 colonne. Il contiendra des float :

histogramme = np.zeros((256,1),np.float32)

Pour afficher un histogramme, vous utiliserez le module pyplot du package matplotlib

In [ ]:
import cv2
import numpy as np
import matplotlib.pyplot as plt

image = cv2.imread('...')
histogramme = np.zeros((256,1),np.float32)

# ...

cv2.imshow('image',image)

plt.figure()
plt.title("Histogram")
plt.xlabel("Bins")
plt.ylabel("# of Pixels")
plt.plot(histogramme)
plt.xlim([0, 255])
plt.show(block=False)

cv2.waitKey(0)
plt.close('all')
cv2.destroyAllWindows()

En cas de souci avec Matplotlib dans les salles de Tps, voici une fonction permettant de dessiner un histogramme dans une image OpenCV :

In [ ]:
import cv2
import numpy as np

def plotHistogramImage(hist, nbins, color, width, height, result = None):
    if result is None:
        result = np.zeros((height, width, 3))
    norm_hist = hist * ((height-10) / np.max(hist))
    norm_hist = norm_hist.astype(int)
    for i in range(1,nbins):
        x1 = int((i-1) * width / nbins)
        x2 = int((i) * width / nbins)
        cv2.line(result, (x1, height - norm_hist[i-1][0]-5), (x2, height - norm_hist[i][0]-5), color)
    return result

image = cv2.imread('...')
histogramme = np.zeros((256,1),np.float32)

cv2.imshow('image',image)
cv2.imshow('histogramme',plotHistogramImage(histogramme,256,(255,0,0),600,300))
cv2.waitKey(0)

Cette fonction renvoie en résultat une image noire avec le dessin de l'histogramme. Elle prend en argument :

  • l'histogramme à dessiner
  • le nombre de bins dans l'histogramme
  • la couleur de dessin
  • la largeur de l'image résultat
  • la hauteur de l'image résultat
  • une image [optionnel] à utiliser comme résultat (au lieu d'en créer une nouvelle). Ceci permet de dessiner plusieurs histogrammes sur une même image.

Exercice 1¶

Afin de vous familiariser avec les parcours d'images, les fonctions suivantes seront à faire sans utiliser la bibliothèque OpenCV.

Chaque fonction devra être testée en affichant l'image originale et l'histogramme correspondant.

Question 1 : Utiliser la fonction cv2.calcHist pour calculer l'histogramme d'une image en niveaux de gris passée en argument. L'histogramme sera calculé pour l'ensemble des valeurs possibles (de 0 à 255).

Question 2 : Utiliser la fonction cv2.normalize afin appliquer l'algorithme d'étalement d'histogrmme vu en cours.

Question 3 : Afficher les histogramme de l'image avant et après la normalisation.


Exercice 2¶

Question 1 : Ecrire la fonction histogrammeCumule permettant, à partir d'un histogramme passé en argument, de calculer l'histogramme cumulé.

Question 2 : Ecrire la fonction histogrammeNormalise permettant, à partir d'un histogramme (normal ou cumulé) et des dimensions de l'image, de calculer l'histogramme normalisé.

Question 3 : Utiliser la fonction cv2.equalizeHist afin d'appliquer l'algorithme d'égalisation d'histogrmme vu en cours.

Question 4 : Afficher les histogrammes de l'image avant et après l'éqalisation. Obtient-on un histogramme plat ?

Question 5 : Afficher les histogrammes cumulés normalisés de l'image avant et après l'éqalisation. Obtient-on un histogramme linéaire ?


Exercice 3 : Correction gamma¶

L'objectif de cet exercice est d'appliquer une transformation aux luminances d'une image. Cette transformation est appelée correction gamma et se modélise de la façon suivante :

Icorr = 255 * (Iinit / 255) ^ (1 / gamma)

Question 1 : En utilisant la fonction cv2.equalizeHist, écrire la fonction egalisationLuminance qui, sur l'image couleur passée en argument, va égaliser les luminances de cette image. Le résultat sera donnée dans une nouvelle image.

Question 2 : Ecrire une fonction correctionGamma(image,gamma) réalisant la correction gamma des luminances d'une image couleur passée en argument. Cette fonction devra renvoyer l'image corrigée en résultat.

La mise en oeuvre naïve consiste à faire une double boucle de parcours de l'image pour modifier les luminances. Dans le cas de cet algorithme une valeur de luminance Iinit est toujours remplacée par une même valeur Icorr.

Dans ce cas, il est possible de construire un tableau de correspondance associant Iinit à Icorr. Ce tableau contiendra, dans la case d'incide Iinit la valeur Icorr. Une fois ce tableau construit pour toutes les valeurs possibles de Iinit, il suffit d'utiliser la fonction cv2.LUT qui s'occupera de faire la conversion de toutes les valeurs.

Question 3 : Utiliser la fonction mse (tp intro) pour comparer les deux images obtenues.

Votre programme devra permettre de modifier le coefficient gamma à l'aide d'un slider OpenCV.

Filtrage d'images¶

Dans cette partie du TP, les fonctions OpenCV utilisées/utilisables sont les suivantes :

  • cv2.blur(src, ksize[, dst[, anchor[, borderType]]]) -> dst
  • cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) -> dst
  • cv2.medianBlur(src, ksize[, dst]) -> dst
  • cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) -> dst
  • cv2.getStructuringElement(shape, ksize[, anchor]) -> retval

Préambule¶

Afin de pouvoir tester les fonctions de filtrages, voici quelques fonctions permettant d'ajouter du "bruit" à une image selon différentes méthodes.

In [ ]:
def bruitGaussien(image,moyenne,variance):
    if len(image.shape)==3:
        row,col,ch= image.shape
        sigma = variance**0.5
        gauss = np.random.normal(moyenne,sigma,(row,col,ch))
        gauss = gauss.reshape(row,col,ch)
        noisy = image + gauss
        return noisy.astype(np.uint8)
    else:
        row,col= image.shape
        sigma = variance**0.5
        gauss = np.random.normal(moyenne,sigma,(row,col))
        print(np.min(gauss),np.max(gauss))
        gauss = gauss.reshape(row,col)
        noisy = image + gauss
        return noisy.astype(np.uint8)

def bruitPoivreEtSel(image,prob):
    row= image.shape[0]
    col = image.shape[1]
    out = image.copy()
    if len(image.shape) == 2:
        black = 0
        white = 255            
    else:
        colorspace = image.shape[2]
        if colorspace == 3:  # RGB
            black = np.array([0, 0, 0], dtype='uint8')
            white = np.array([255, 255, 255], dtype='uint8')
        else:  # RGBA
            black = np.array([0, 0, 0, 255], dtype='uint8')
            white = np.array([255, 255, 255, 255], dtype='uint8')
    probs = np.random.random(out.shape[:2])
    # Pepper mode
    out[probs < (prob / 2)] = black
    # Salt mode
    out[probs > 1 - (prob / 2)] = white
    return out.astype(np.uint8)

def bruitPoisson(image):
    vals = len(np.unique(image))
    vals = 2 ** np.ceil(np.log2(vals))
    noisy = np.random.poisson(image * vals) / float(vals)
    return noisy.astype(np.uint8)

def bruitSpeckle(image):
    gauss = np.random.normal(0,1,image.size)
    if len(image.shape)==3:
        row,col,ch = image.shape
        gauss = gauss.reshape(row,col,ch).astype(np.uint8)
        noisy = image + image * gauss
        return noisy.astype(np.uint8)
    else:
        row,col = image.shape
        gauss = gauss.reshape(row,col).astype(np.uint8)
        noisy = image + image * gauss
        return noisy.astype(np.uint8)

Exercice 4¶

Ecrire un programme permettant de comparer le floutage d'une image en utilisant les 3 méthodes : cv2.blur, cv2.GaussianBlur et cv2.medianBlur.

Vous devrez, dans un premier temps, ajouter du bruit à une image préalablement chargée.

Votre programme devra permettre de modifier la taille du noyau de filtrage grace à un slider (la même taille de noyau sera utilisée pour les 3 filtres).

Pour comparer les résultats, utiliser la fonction mse donnée en fin du tp d'introduction.


Exercice 5¶

Les 3 fonctions utilisées précédemment permettent d'utiliser des filtres usuels de taille carrée (ou rectangulaire).

Il est possible d'appliquer des filtres de taille et de forme non usuelles. Pour cela, on utilise la fonction cv2.getStructuringElement. Cette fonction va renvoyer une matrice 2d dont les éléments inclus dans la forme définie par le paramètre shape sont initialisés à 1 alors que les autres seront à 0.

Le noyau de filtrage ainsi obtenu peut être appliqué à une image en utilisant la fonction cv2.filter2D.

Utiliser ces fonction pour appliquer un filtre moyen en croix de taille 5x5 sur une image. Comparer les résultats avec ceux obtenus par un filtrage moyen de même taille en utilisant la fonction cv2.blur.

Seuillage d'image¶

Dans cette partie du TP, les fonctions OpenCV utilisées/utilisables sont les suivantes :

  • cv2.threshold(src, thresh, maxval, type[, dst]) -> retval, dst
  • cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) -> hist

Exercice 6¶

Ecrire un programme permettant de binariser une image en niveaux de gris en utilisant la fonction cv2.threshold.

Le seuil de binarisation devra être paramétrable grace à un slider.

Tester les différentes valeurs possibles du paramètre type de cette fonction.


Exercice 7¶

Dans l'exercice précédent, la binarisation de l'image se fait à pratir d'une unique valeur de luminance.

Une autre méthode de binarisation permettant plus facilement de dissocier un objet de son environnement consiste à analyser la répartition des couleurs d'un élément (le fond ou l'objet) et de rechercher dans l'image les zones ayant à peu près la même répartition de couleur. C'est le principe général du ChromaKeying utilisé lors de captations de vidéos sur fond vert comme au cinéma.

Pour cela, il suffit de :

  • sélectionner une zone de l'élément à discerner (par exemple avec la souris)
  • de calculer l'histogramme des composantes purement couleur de l'image mais uniquement sur la zone sélectionnée
  • d'utiliser la fonction cv2.calcBackProject qui permet de calculer, pour chaque pixel, la distance entre l'histogramme de son voisinnage et un histogramme de référence.